#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/select.h>
#include <memory>
#include <algorithm>
#include <sys/poll.h>
#include "Socket.hpp"
#include "Log.hpp"

using namespace SocketModule;
using namespace LogModule;

class PollServer
{
    const static int size = 4096;
    const static int defaultfd = -1;

public:
    PollServer(int port)
        : _listensock(std::make_unique<TcpSocket>()),
          _isrunning(false)
    {
        // 完成套接字初始化操作
        _listensock->BuildTcpSocketMethod(port);

        // 初始化pollfd结构体中变量
        for (int i = 0; i < size; i++)
        {
            _fds[i].fd = defaultfd;
            _fds[i].events = 0;
            _fds[i].revents = 0;
        }

        // 先把listen套接字设置进去
        _fds[0].fd = _listensock->Fd();
        _fds[0].events = POLLIN;
    }

    // 启动服务器
    void Start()
    {
        int timeout = -1;
        _isrunning = true;
        while (_isrunning)
        {
            PrintFd();
            int n = poll(_fds, size, timeout);
            switch (n)
            {
            case -1:
                // select出错了
                LOG(LogLevel::ERROR) << "select error";
                break;
            case 0:
                // 表示超时了
                LOG(LogLevel::INFO) << "time out...";
                break;
            default:
                // 到这说明有事件就绪了
                // 这个事件不仅仅指有新链接到来了，有可能是读事件就绪
                LOG(LogLevel::DEBUG) << "有事件就绪了...";
                Dispatcher(); // 此时就可以处理就绪的事件，使用Dispatcher任务派发器
                break;
            }
        }
        _isrunning = false;
    }

    // Dispatcher任务派发器
    void Dispatcher()
    {
        // 这个事件不仅仅指有新链接到来了，也有可能是读事件就绪
        // 所以在这里得进行判断，指定的fd在被修改之后的fds中就证明该fd读事件就绪了
        for (int i = 0; i < size; i++)
        {
            if (_fds[i].fd == defaultfd)
            {
                continue;
            }
            // fd合法，于是进行判断是否就绪，看revent&上POLLIN是否不为0
            if (_fds[i].revents & POLLIN)
            {
                // 到这说明这个fd读事件就绪了
                // 可现在有个问题，对于listensockfd，新连接到来时也是读事件就绪呀
                // 普通的sockfd数据到来，也是读事件就绪，要怎么进行区分呢？
                // 很简单，我们判断一下就好啦
                if (_fds[i].fd == _listensock->Fd())
                {
                    // listensockfd 有新连接到来
                    Accepter(); // 那就执行获取新连接的方法
                }
                else
                {
                    // sockfd 有数据到来
                    Recver(i); // 那就执行读取数据的方法
                }
            }
        }
    }

    // 获取新连接的方法
    // 也叫连接管理器
    void Accepter()
    {
        InetAddr client;
        // 此时是在读事件就绪时也就是有连接时才调用的accept，所以此时不会发生阻塞，直接读取
        // 这就是多路转接
        int sockfd = _listensock->Accept(&client);
        if (sockfd >= 0)
        {
            // 获取新连接到来成功，然后我们可以直接read/recv()不？
            // 答案是当然不行，因为我们的sockfd是否读事件就绪是不清楚的
            // 贸然调用读取接口不还是会出现阻塞的情形咩
            // 这里是poll最清楚未来sockfd上是否有事件就绪

            // 所以在这里得先将新的sockfd加入到pollfd结构体数组中
            LOG(LogLevel::DEBUG) << "get a new link,sockfd: " << sockfd
                                 << "client is: " << client.StringAddr();
            int pos = 0;
            for (; pos < size; pos++)
            {
                if (_fds[pos].fd == defaultfd)
                    break; // 表示该位置可以被设置
            }
            // 出来判断一下pos的大小
            if (pos == size)
            {
                // 说明没有位置可以放新来的fd了，也就是服务器满了
                LOG(LogLevel::WARNING) << "poll server full";
                close(sockfd); // 那么我们只能结束掉这个连接了
                // 但是如果我们使用的是结构体指针，那么在这里可以进行动态扩容
            }
            else
            {
                // 找到了合法的位置
                _fds[pos].fd = sockfd;
                _fds[pos].events = POLLIN;
                _fds[pos].revents = 0;
            }
        }
    }

    // 读取数据的方法
    void Recver(int pos)
    {
        // 到这的时候poll确保这个fd中有数据可以读取的，读事件是就绪的
        // 所以在这里调用read/recv进行读取操作时并不会阻塞
        char buffer[1024];
        // 注意：这里的recv由于是tcp协议，所以还是会有老问题的——可能读取的数据不完整！
        ssize_t n = recv(_fds[pos].fd, buffer, sizeof(buffer), 0);
        if (n > 0)
        {
            // 读取成功
            buffer[n] = 0;
            std::cout << "client say: " << buffer << std::endl;
        }
        else if (n == 0)
        {
            // 表示客户端退出了
            LOG(LogLevel::INFO) << "client quit...";
            close(_fds[pos].fd);
            _fds[pos].fd = defaultfd;
            _fds[pos].events = _fds[pos].revents = 0;
        }
        else
        {
            // 读取失败
            LOG(LogLevel::ERROR) << "recv error";
            close(_fds[pos].fd);
            _fds[pos].fd = defaultfd;
            _fds[pos].events = _fds[pos].revents = 0;
        }
    }

    void PrintFd()
    {
        std::cout << "_fds[]: ";
        for (int i = 0; i < size; i++)
        {
            if (_fds[i].fd == defaultfd)
            {
                continue;
            }
            std::cout << _fds[i].fd << " ";
        }
        std::cout << "\r\n";
    }

    // 停止服务器
    void Stop()
    {
        _isrunning = false;
    }

    ~PollServer() {}

private:
    std::unique_ptr<Socket> _listensock;
    bool _isrunning;
    // struct pollfd *_fd;      // 这样可以支持动态扩容
    struct pollfd _fds[size]; // 这样就是传定长数组给poll
};